#!/usr/local/bin/python3
# *_* coding: UTF-8 *_*
# @IDE: PyCharm
# @Version: Python3.97
# @Author: Kendrick.Kc
# @Email: 509556985@qq.com
# @File: main.py
# @Inst: 策略启动入口
# @Time: 2023/6/23 15:12
# -----


import django

django.setup()  # 为在子进程中调用django应用，需在子进程启动时初始化django
from basic.models import ProcessNumber

import time
import os
import signal
import platform
import traceback
from multiprocessing import Process, Lock
from binance_dgs.settings import Parameter
from binance_dgs.get_volatility import get_db_volatility
from binance_dgs.extract import ExtractSymbol
from utils.timestamp import now_time_stamp
from django.http import JsonResponse
from django.forms.models import model_to_dict
from binance_dgs.threadings import TradingAppStart


class DGSProcessAppStart:

    def __init__(self, binance, request_data):
        self.binance = binance  # 实例化接收到的中间接口类
        self.request_data = request_data  # 前端请求过来的data

    def is_model(self):

        """
        根据有无持仓做出相应操作
        :return:
        """

        self.binance.logger_info(f"正在初始化手动停止程序的{self.binance.parameter.db_coll_program}缓存数据......")
        self.binance.cache_set(self.binance.parameter.db_coll_program, False)  # 初始化手动停止程序的数据表
        self.binance.logger_info(f"初始化手动停止程序的{self.binance.parameter.db_coll_program}缓存数据完成.")

        self.binance.logger_info(f"准备获取{self.binance.parameter.db_coll_trading_symbol}缓存数据检查是否存在交易......")
        if self.binance.pan_openings["pan"]:    # 模拟盘
            res_trading = self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol) or []
        else:       # 实盘
            if self.binance.get_user_position()["number"] <= 0:
                res_trading = []
                self.binance.cache_set(self.binance.parameter.db_coll_trading_symbol, [])
                # 初始化本地金额
            else:
                res_trading = self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol) or []

        if res_trading:  # 持仓
            self.binance.logger_info(f"检查{self.binance.parameter.db_coll_trading_symbol}缓存数据存在交易数据.")
            res_trading_symbol = self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol)
            trading = [self.binance.cache_get(f"{self.binance.exchange_strategy}_{item['symbol']}") for item in res_trading_symbol]
            return trading

        # 空仓
        self.binance.logger_info(f"检查{self.binance.parameter.db_coll_trading_symbol}缓存数据不存在交易.")
        # 建立账户金额基础数据
        ex_res = self.binance.rest("while_info", "account")
        for _ in ex_res['assets']:
            if _['asset'] == self.binance.parameter.ccy:
                self.binance.logger_info(f"正在为{self.binance.strategy_name}建立{self.binance.parameter.db_coll_balance}缓存数据......")
                balance = float(_['availableBalance']) if self.binance.pan_openings["pan"] == '0' else self.binance.parameter.this_balance
                data = {
                    self.binance.parameter.db_coll_balance: balance,
                    self.binance.parameter.db_coll_balance_init: balance,
                    self.binance.parameter.db_coll_balance_timeStamp: now_time_stamp()
                }
                self.binance.cache_set(self.binance.parameter.db_coll_balance, data)
                self.binance.logger_info(f"为{self.binance.strategy_name}建立{self.binance.parameter.db_coll_balance}缓存数据完成.")
                break

        # 设置网格，并获取需要交易的U数
        self.binance.logger_info(f"进入高波动率交易对采集程序......")
        res_vol = get_db_volatility(self.binance)
        # res_vol = [
        #     {"symbol": "ETHUSDT", "rv": 0.022100794139514834, "depth": 1},
        #     {"symbol": "BTCUSDT", "rv": 0.022100794139514834, "depth": 1},
        #     {"symbol": "LTCUSDT", "rv": 0.022100794139514834, "depth": 1},
        #     {"symbol": "XRPUSDT", "rv": 0.022100794139514834, "depth": 1},
        #     {"symbol": "BNBUSDT", "rv": 0.022100794139514834, "depth": 1},
        #     {"symbol": "DOGEUSDT", "rv": 0.022100794139514834, "depth": 1},
        # ]
        for i in range(len(res_vol)):
            try:
                if len(res_trading) < self.binance.parameter.trading_count_number:
                    res_dgs = ExtractSymbol(self.binance, res_vol[i]['symbol'])
                    res_dgs = res_dgs.extract()
                    if res_dgs:
                        self.binance.logger_info(
                            f"{res_vol[i]['symbol']}交易对计算并生成{self.binance.strategy_name}数据完成.")
                        res_trading.append(res_dgs)
            except:
                self.binance.logger_info(f"计算{res_vol[i]['symbol']}交易对DGS数据异常: {traceback.format_exc()}")
                continue
        return res_trading

    @staticmethod
    def inst_symbol(inst_id, process=Parameter.cpu_number):
        """
        交易对根据核心数平分
        :param inst_id: 交易对
        :param process: CPU核数
        :return: 嵌套列表
        """
        if len(inst_id) % process == 0:
            cnt = len(inst_id) // process
        else:
            cnt = len(inst_id) // process + 1

        for i in range(0, process):
            yield inst_id[i * cnt:(i + 1) * cnt]

    def trading_ticker_price(self):
        """
        获取所有交易对最新价格并存入数据库（子线程死循环）
        :return: 接收到的中间接口类
        """

        while True:
            try:

                time.sleep(1)
                """ 将所有交易对最新价格以列表类型写入数据库缓存 """
                res = self.binance.rest("while_info", "ticker_price")
                self.binance.cache_set(self.binance.parameter.db_coll_symbol_new_ticker_price, res)

            except Exception as e:
                self.binance.logger_info(f"获取所有币种最新价格(子线程错误): {str(e)} \n 跳过当前，重新获取......")

    def app_start(self):
        """
        策略启动
        :return:
        """

        self.binance.logger_info(f"{self.binance.strategy_name}策略程序启动......")
        # 判断账户是否足以启动多进程多线程策略
        db_balance = self.binance.cache_get(self.binance.parameter.db_coll_balance)
        if db_balance and db_balance[self.binance.parameter.db_coll_balance_init] < self.binance.parameter.this_balance:
            if not self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol):
                self.binance.logger_error(
                    f"交易所账户余额不足, 程序最少需要{self.binance.parameter.this_balance}个{self.binance.parameter.ccy}继续运行...... ")
                return JsonResponse(
                    {'code': 31403, 'msg': f'账户余额不足{self.binance.parameter.this_balance}{self.binance.parameter.ccy}.'})

        # 保存状态
        process_obj = ProcessNumber.objects.update_or_create(exchange=self.binance.exchange_name,
                                                             strategy=self.binance.strategy_name,
                                                             defaults={
                                                                 "status": self.request_data["status"],
                                                                 "pan_opening": self.request_data["pan_opening"],
                                                             })
        tp = Process(target=self.process_start)     # 构建数据时间长，另起子进程构建
        tp.start()
        self.binance.cache_set(self.binance.parameter.db_coll_construct_process, tp.pid)      # 缓存构建时的进程PID
        return JsonResponse({'data': model_to_dict(process_obj[0]), 'code': 200, 'msg': 'ok'})

    def process_start(self):
        try:
            dgs = self.inst_symbol(inst_id=self.is_model())

            process_number = []
            # 死循环实时获取交易所ticker price数据进程
            tp = Process(target=self.trading_ticker_price)
            tp.start()
            process_number.append(tp.pid)
            # 等待实时价格缓存存在数据后再进入多进程多线程阶段
            while True:
                if self.binance.cache_get(self.binance.parameter.db_coll_symbol_new_ticker_price):
                    break

            res = True
            m_lock = Lock()  # 进程锁
            for item in dgs:
                if item:
                    res = False
                    p = Process(target=TradingAppStart, args=(item, m_lock, self.binance,))
                    p.start()
                    process_number.append(p.pid)  # 将子进程号写入数据库中

            if res:
                self.binance.logger_error(f"进程交易对为空，问题一般为账户余额({self.binance.parameter.ccy})不足.")
                return JsonResponse({'code': 31501, 'msg': '进程交易对列表为空'})

            # 保存进程号
            process_obj = ProcessNumber.objects.filter(exchange=self.binance.exchange_name,
                                                       strategy=self.binance.strategy_name).first()
            process_obj.process = process_number
            process_obj.timestamp = now_time_stamp()
            process_obj.save()

            return JsonResponse({'data': model_to_dict(process_obj), 'code': 200, 'msg': 'ok'})

        except:
            self.binance.logger_info(f"机器人启动失败: {traceback.format_exc()}")
            return JsonResponse({'code': 31500, 'msg': '机器人错误.'})

    def position_stop(self):
        """
        平仓停止
        :return:
        """
        try:
            process_obj = ProcessNumber.objects.filter(exchange=self.binance.exchange_name,
                                                       strategy=self.binance.strategy_name).first()
            process_obj.status = self.request_data["status"]
            process_obj.timestamp = now_time_stamp()
            process_obj.save()
            self.binance.cache_set(self.binance.parameter.db_coll_program, True)  # 初始化手动停止程序的数据表
            self.binance.logger_info(f"设置{self.binance.parameter.db_coll_program}缓存数据【平仓停止】完成.")
            return JsonResponse({'data': model_to_dict(process_obj), 'code': 200, 'msg': 'ok'})
        except:
            self.binance.logger_error(f"机器人平仓停止错误: {traceback.format_exc()}")
            return JsonResponse({'code': 31502, 'msg': '机器人平仓停止错误.'})

    def now_stop(self):
        """
        立即停止
        :return:
        """

        try:
            process_obj = ProcessNumber.objects.filter(exchange=self.binance.exchange_name,
                                                       strategy=self.binance.strategy_name).first()
            process_obj.status = self.request_data["status"]
            process_obj.timestamp = now_time_stamp()

            sig = signal.SIGINT if platform.system() == "Windows" else signal.SIGKILL
            try:
                for pid in process_obj.process:
                    os.kill(pid, sig)

                # 删除构建进程PID
                cp = self.binance.cache_get(self.binance.parameter.db_coll_construct_process)
                if cp:
                    os.kill(cp, sig)
                    self.binance.cache_delete(self.binance.parameter.db_coll_construct_process)
            except:
                self.binance.logger_warning(f"kill子进程错误: {traceback.format_exc()}")
            process_obj.process = []
            process_obj.save()

            self.binance.logger_info(f"【立即停止】成功.")
            return JsonResponse({'data': model_to_dict(process_obj), 'code': 200, 'msg': 'ok'})
        except:
            self.binance.logger_error(f"机器人立即停止错误: {traceback.format_exc()}")
            return JsonResponse({'code': 31503, 'msg': '机器人立即停止错误.'})
